﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using AlgorithmResult;
using System.Diagnostics;
using SimilarityToAncientCodon;

namespace CodonSegDP
{

  


	public class DynamicProgrammingAlg
	{
        private Boolean needToStop = false;
        private int codonSize = 3;
        public Boolean NeedToStop
        {
            get { return needToStop; }
            set { needToStop = value; }
        }
        public event ProgressEventHandler ProgressHandler = null;
        public enum NUCLEOTID { A = 0, T = 1, C = 2, G = 3 };
        private int[][] AllocDimensionArray(int rows, int columns)
        {
            int[][] dimensionArray = new int[rows][];
            for (int i = 0; i < rows; i++)
            {
                dimensionArray[i] = new int[columns];
            }
                return dimensionArray;
        }
        public Result TripletBounderiesSegmentator(String Text, int _minSegmentSize,int _codonSize=3)

        {
          int  curPostion=0;
          codonSize = _codonSize;
          int[][] SegLeftNuclDistr = AllocDimensionArray(4, _codonSize); //SegLeftNuclDistr[4][3]

          int[][] SegRightNuclDistr = AllocDimensionArray(4, _codonSize);
          double[] costSegment = new double[Text.Length];
          String[] tmpArchreTriplets = new String[Text.Length];
          int i=0;
          long[] BoundariesSet = new long[Text.Length / _minSegmentSize - 1];
          String[] ArchreTriplets = new String[Text.Length / _minSegmentSize - 1];
            
          bool needNewIteration=false;
          int boundery_index = 0, costSegmentIndx=0;
          int lastMaxCostSegment = 0;
          int lastBoundaryValueIndex = 0;
          long tmpLastBoundIndx = 0;
          int maxCostSegment = 0;
          Dictionary<double, int> dict = new Dictionary<double, int>();
          Stopwatch sw = new Stopwatch();
          sw.Start();
          while (curPostion != Text.Length && (curPostion + (_minSegmentSize * 2) * _codonSize) < Text.Length && !needToStop)
            {
               
                    needNewIteration=false;
                    i = 0;


                    tmpLastBoundIndx=FindLastBoundaryIndx(BoundariesSet);
                    {
                        while (i < _minSegmentSize)
                        {
                            AddTripletToSegment(SegLeftNuclDistr, Text.Substring(curPostion, _codonSize));
                            AddTripletToSegment(SegRightNuclDistr, Text.Substring(curPostion + _minSegmentSize * _codonSize, _codonSize));
                            curPostion += _codonSize;
                            i++;
                        }
                        costSegment[costSegmentIndx] = (double)EvaluateCostFunction(SegLeftNuclDistr, ((curPostion - (int)tmpLastBoundIndx) / _codonSize), ref tmpArchreTriplets[costSegmentIndx])
                                                            + EvaluateCostFunction(SegRightNuclDistr, _minSegmentSize);
                        dict[costSegment[costSegmentIndx]] = curPostion;
                        costSegmentIndx++;
                    }
                  do{
                  
                      i = 1;
                      while (i < _minSegmentSize && (curPostion + _minSegmentSize * 2 * _codonSize) < Text.Length)
                        {
                            
                       //    
                            AddTripletToSegment(SegLeftNuclDistr, Text.Substring(curPostion, _codonSize));// add one triplet from right segment
                            removeTripletFromSegment(SegRightNuclDistr, Text.Substring(curPostion, _codonSize));//remove first triplet from right segment

                            AddTripletToSegment(SegRightNuclDistr, Text.Substring(curPostion + _minSegmentSize * _codonSize, _codonSize));  //add one triplet from next
                                                        
                            curPostion += _codonSize;
                            i++;
                            costSegment[costSegmentIndx] = (double)EvaluateCostFunction(SegLeftNuclDistr, (curPostion - (int)tmpLastBoundIndx) / _codonSize, ref tmpArchreTriplets[costSegmentIndx])
                                + EvaluateCostFunction(SegRightNuclDistr, _minSegmentSize);
                            dict[costSegment[costSegmentIndx]] = curPostion;
                            costSegmentIndx++;
                        }
                       maxCostSegment = FindMaxCostSegment(costSegment);
                      if (maxCostSegment != lastMaxCostSegment)
                        { //if we find boundary better than first one

                            lastMaxCostSegment = maxCostSegment;
                            curPostion = dict[costSegment[lastMaxCostSegment]];// (_minSegmentSize * _codonSize + (maxCostSegment) * _codonSize) + lastBoundaryValueIndex;
                          needNewIteration = true;
                            
                            i = 0;
                        }
                        else {

                            curPostion = dict[costSegment[maxCostSegment]];//(_minSegmentSize * _codonSize + maxCostSegment * _codonSize) + lastBoundaryValueIndex;// (int)FindLastBoundaryIndx(BoundariesSet);
                            lastBoundaryValueIndex = curPostion;
                            ArchreTriplets[boundery_index] = tmpArchreTriplets[maxCostSegment];
                            if ((boundery_index > 0 && ArchreTriplets[boundery_index - 1] == ArchreTriplets[boundery_index]) == false)
                            {

                                BoundariesSet[boundery_index++] = curPostion;//curPostion - MinSegmentSize + FindMaxCostSegment(costSegment);
                                if (ProgressHandler != null)
                                {
                                    ProgressHandler(this, new progressEvent(curPostion, ArchreTriplets[boundery_index-1]));
                                }
                            }
                            else {
                                ArchreTriplets[boundery_index] = String.Empty;
                                BoundariesSet[boundery_index-1] = curPostion;
                            }
                          
                            needNewIteration = false;
                            lastMaxCostSegment = 0;
                            ZeroSegNuclDistribution(SegLeftNuclDistr);
                            ZeroSegNuclDistribution(SegRightNuclDistr);
                            
                          // ZeroArcheTripletsStrings(tmpArchreTriplets, ref costSegmentIndx);
                          // ZeroCosegment(costSegment, ref costSegmentIndx);
                            tmpArchreTriplets = new String[(Text.Length - lastBoundaryValueIndex) ]; 
                            costSegment = costSegment = new double[Text.Length - lastBoundaryValueIndex];
                            dict = new Dictionary<double, int>();  
                          costSegmentIndx = 0;
                           
                          
                        }
                }while(needNewIteration);
             
             
          
          }


        
          while (curPostion + codonSize <= Text.Length)
          {
              AddTripletToSegment(SegLeftNuclDistr, Text.Substring(curPostion, codonSize));
              curPostion += _codonSize;


          }
          String ArcheTriplet = String.Empty;
          EvaluateCostFunction(SegLeftNuclDistr, ((curPostion - (int)FindLastBoundaryIndx(BoundariesSet)) / codonSize), ref ArcheTriplet);
          if (ArcheTriplet == ArchreTriplets[boundery_index - 1])
          {
              BoundariesSet[boundery_index - 1] = curPostion;
          }
          else {
              BoundariesSet[boundery_index] = curPostion;
              ArchreTriplets[boundery_index] = ArcheTriplet;
              boundery_index++;
 
          }
          Console.WriteLine("Finish Time: {0} ms", sw.ElapsedMilliseconds);

          Result bestResultOfSegGen = new Result(BoundariesSet, ArchreTriplets, boundery_index);
          FragmentSimToAncientCodon simToAncientCodon = new FragmentSimToAncientCodon(bestResultOfSegGen, Text, codonSize);
          return bestResultOfSegGen;
}

        private void ZeroArcheTripletsStrings(string[] tmpArchreTriplets, ref int costSegmentIndx)
        {
            for (int i = 0; i < costSegmentIndx; i++)
            {
                tmpArchreTriplets[i] = String.Empty ;
            }
        }

        private long FindLastBoundaryIndx(long[] BoundariesSet)

        {
            long index = BoundariesSet.LastOrDefault<long>(num => num > 0);
       //     for(int i=0;i<BoundariesSet.Length)
            return index;
        }

        private void ZeroSegNuclDistribution(int[][] SegLeftNuclDistr)
        {
            for (int i = 0; i < SegLeftNuclDistr.Length; ++i)
                for (int j = 0; j < SegLeftNuclDistr[i].Length; j++)
                    SegLeftNuclDistr[i][j] = 0;
        }

        private void ZeroCosegment(double[] costSegment, ref int costSegmentIndx)
        {
            for (int i = 0; i < costSegmentIndx; i++)
            {
                costSegment[i] = 0;
            }
            costSegmentIndx = 0;
        }

        private void AddTripletToSegment(int[][] segmentDest,String triplet)
        {
 	
            for(int index=0;index<codonSize;index++){
                segmentDest[CnvtNucliotideToIndx(triplet[index])][index]++;
            }
        }
        private int CnvtNucliotideToIndx(char nucliotide)
        {
            int index=-1;
            if (Enum.IsDefined(typeof(NUCLEOTID),Convert.ToString(nucliotide).ToUpper()))
                index = (int)Enum.Parse(typeof(NUCLEOTID), Convert.ToString(nucliotide), true);

            return index;
        }

        private void removeTripletFromSegment(int[][] segmentDest, string triplet)
        {
            for (int index = 0; index < codonSize; index++)
            {
                segmentDest[CnvtNucliotideToIndx(triplet[index])][index]--;
            }
        }
        private int FindMaxCostSegment(double[] costsegment)
        {
            int max_cost_index=0;
            double max_value = 0;
          
            for (int i = 0; i < costsegment.Length; ++i)
            {
                if (costsegment[i] >= costsegment[max_cost_index])
                {
                    max_cost_index = i;
                }
            }
            return max_cost_index;
 
        }
        private double EvaluateCostFunction(int[][] SegmentOfTriplets, int segmentSize, ref String archeTriplet)
        {
            int cost=0;
            int[] maxNuclDist = new int[codonSize];
            int i=0,j=0;
            archeTriplet = String.Empty;
            String nucl = String.Empty ;
            //triplet size is 3
            while(i< codonSize)
            {
                j = 0;
                while(j<4)
                {//4-number of nucleotides
                    if (maxNuclDist[i] < SegmentOfTriplets[j][i])
                    {
                        maxNuclDist[i] = SegmentOfTriplets[j][i];
                        nucl=((NUCLEOTID)j).ToString();
                    }
                    j++;
                }
                 cost += maxNuclDist[i];
                 archeTriplet += nucl;
                 i++;
            }
            return (double)cost / segmentSize;// * ((double)segmentSize/1000));
        }
        private double EvaluateCostFunction(int[][] SegmentOfTriplets, int segmentSize)
        {
            int cost = 0;
            int[] maxNuclDist = new int[codonSize];
            int i = 0, j = 0;
            
            //triplet size is 3
            while (i < codonSize)
            {
                j = 0;
                while (j < 4)
                {//4-number of nucleotides
                    if (maxNuclDist[i] < SegmentOfTriplets[j][i])
                        maxNuclDist[i] = SegmentOfTriplets[j][i];
                    j++;
                }
                cost += maxNuclDist[i];
                i++;
            }
                return (double)cost / segmentSize;// * ((double)segmentSize / 1000));
        }



	}
}
